--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Commit 3cb1a049172ee24a650cf4b592bfbdd8cdc07e52
Parents : 68498d3
Author : Ivan <ivan@quad4.io>
Signature : Signature validation error
Date : 2026-04-16T02:26:27-05:00
feat(android): update MainActivity with loading logo and improved startup messaging; streamline WebView visibility handling during loading phases
Changes
2 files changed, 54 insertions(+), 10 deletions(-)
Diff
diff --git a/android/app/src/main/java/com/meshchatx/MainActivity.java b/android/app/src/main/java/com/meshchatx/MainActivity.java
index 3af0adf1..475162cf 100644
--- a/android/app/src/main/java/com/meshchatx/MainActivity.java
+++ b/android/app/src/main/java/com/meshchatx/MainActivity.java
@@ -20,6 +20,7 @@ import android.webkit.WebResourceRequest;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
+import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
@@ -38,10 +39,10 @@ import java.util.List;
public class MainActivity extends AppCompatActivity {
private WebView webView;
private ProgressBar progressBar;
+ private ImageView loadingLogo;
private TextView loadingText;
private TextView errorText;
- private static final String SERVER_URL_HTTPS = "https://127.0.0.1:8000";
- private static final String SERVER_URL_HTTP = "http://127.0.0.1:8000";
+ private static final String SERVER_URL = "https://127.0.0.1:8000";
private static final int SERVER_PORT = 8000;
private static final int RUNTIME_PERMISSIONS_REQUEST_CODE = 1001;
private static final int MICROPHONE_WEB_PERMISSION_REQUEST_CODE = 1002;
@@ -53,11 +54,17 @@ public class MainActivity extends AppCompatActivity {
private final Handler mainHandler = new Handler(Looper.getMainLooper());
private PermissionRequest pendingWebPermissionRequest = null;
private ValueCallback<Uri[]> filePathCallback = null;
- private String startupServerUrl = SERVER_URL_HTTPS;
private boolean startupRequestHadLoadError = false;
private boolean startupPageLoaded = false;
private boolean backendFailed = false;
private int connectionAttempts = 0;
+ private static final String[] STARTUP_PHASES = new String[] {
+ "Starting MeshChatX...",
+ "Initializing Reticulum network stack...",
+ "Loading MeshChatX frontend...",
+ "Establishing secure local connection...",
+ "Finalizing startup..."
+ };
private final ActivityResultLauncher<Intent> filePickerLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
@@ -89,8 +96,10 @@ public class MainActivity extends AppCompatActivity {
webView = findViewById(R.id.webView);
progressBar = findViewById(R.id.progressBar);
+ loadingLogo = findViewById(R.id.loadingLogo);
loadingText = findViewById(R.id.loadingText);
errorText = findViewById(R.id.errorText);
+ webView.setVisibility(android.view.View.INVISIBLE);
showLoading("Starting MeshChatX backend...");
if (!Python.isStarted()) {
@@ -119,6 +128,8 @@ public class MainActivity extends AppCompatActivity {
}
startupPageLoaded = true;
mainHandler.removeCallbacksAndMessages(null);
+ webView.setVisibility(android.view.View.VISIBLE);
+ loadingLogo.setVisibility(android.view.View.GONE);
progressBar.setVisibility(android.view.View.GONE);
loadingText.setVisibility(android.view.View.GONE);
errorText.setVisibility(android.view.View.GONE);
@@ -129,6 +140,9 @@ public class MainActivity extends AppCompatActivity {
super.onPageStarted(view, url, favicon);
if (isStartupRequest(url)) {
startupRequestHadLoadError = false;
+ if (!startupPageLoaded) {
+ webView.setVisibility(android.view.View.INVISIBLE);
+ }
}
progressBar.setVisibility(android.view.View.VISIBLE);
}
@@ -138,6 +152,8 @@ public class MainActivity extends AppCompatActivity {
super.onReceivedError(view, request, error);
if (request != null && request.isForMainFrame() && isStartupRequest(request.getUrl().toString())) {
startupRequestHadLoadError = true;
+ view.stopLoading();
+ view.loadUrl("about:blank");
if (backendFailed && !startupPageLoaded) {
CharSequence description = (error != null) ? error.getDescription() : "Unknown error";
showStartupError("WebView failed to load MeshChatX: " + description);
@@ -150,6 +166,8 @@ public class MainActivity extends AppCompatActivity {
super.onReceivedError(view, errorCode, description, failingUrl);
if (isStartupRequest(failingUrl) && !startupPageLoaded) {
startupRequestHadLoadError = true;
+ view.stopLoading();
+ view.loadUrl("about:blank");
if (backendFailed) {
showStartupError("WebView failed to load MeshChatX: " + description);
}
@@ -362,7 +380,7 @@ public class MainActivity extends AppCompatActivity {
}
private boolean isStartupRequest(String url) {
- return url != null && (url.startsWith(SERVER_URL_HTTPS) || url.startsWith(SERVER_URL_HTTP));
+ return url != null && url.startsWith(SERVER_URL);
}
private void scheduleConnectionRetry(String message) {
@@ -383,11 +401,7 @@ public class MainActivity extends AppCompatActivity {
showStartupError("Failed to connect to local MeshChatX server after waiting for startup.");
return;
}
- if (connectionAttempts == (MAX_CONNECTION_ATTEMPTS / 2) && SERVER_URL_HTTPS.equals(startupServerUrl)) {
- startupServerUrl = SERVER_URL_HTTP;
- showLoading("Retrying with HTTP fallback...");
- }
- webView.loadUrl(startupServerUrl);
+ webView.loadUrl(SERVER_URL);
scheduleConnectionRetry("Retrying connection...");
}, retryDelayMs);
}
@@ -403,6 +417,8 @@ public class MainActivity extends AppCompatActivity {
private void showStartupError(String message) {
runOnUiThread(() -> {
mainHandler.removeCallbacksAndMessages(null);
+ webView.setVisibility(android.view.View.INVISIBLE);
+ loadingLogo.setVisibility(android.view.View.GONE);
progressBar.setVisibility(android.view.View.GONE);
loadingText.setVisibility(android.view.View.GONE);
if (errorText != null) {
@@ -417,15 +433,31 @@ public class MainActivity extends AppCompatActivity {
if (startupPageLoaded) {
return;
}
+ webView.setVisibility(android.view.View.INVISIBLE);
+ if (loadingLogo != null) {
+ loadingLogo.setVisibility(android.view.View.VISIBLE);
+ }
progressBar.setVisibility(android.view.View.VISIBLE);
errorText.setVisibility(android.view.View.GONE);
if (loadingText != null) {
- loadingText.setText(message);
+ loadingText.setText(formatLoadingMessage(message));
loadingText.setVisibility(android.view.View.VISIBLE);
}
});
}
+ private String formatLoadingMessage(String fallbackMessage) {
+ int phaseIndex = Math.min(
+ STARTUP_PHASES.length - 1,
+ (connectionAttempts * STARTUP_PHASES.length) / Math.max(1, MAX_CONNECTION_ATTEMPTS)
+ );
+ String phase = STARTUP_PHASES[phaseIndex];
+ if (connectionAttempts == 0) {
+ return phase;
+ }
+ return phase + " (" + connectionAttempts + "/" + MAX_CONNECTION_ATTEMPTS + ")";
+ }
+
@Override
public void onBackPressed() {
if (webView.canGoBack()) {
diff --git a/android/app/src/main/res/layout/activity_main.xml b/android/app/src/main/res/layout/activity_main.xml
index 59ec676e..8b1e01da 100644
--- a/android/app/src/main/res/layout/activity_main.xml
+++ b/android/app/src/main/res/layout/activity_main.xml
@@ -25,6 +25,18 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
+ <ImageView
+ android:id="@+id/loadingLogo"
+ android:layout_width="80dp"
+ android:layout_height="80dp"
+ android:layout_marginBottom="20dp"
+ android:contentDescription="@string/app_name"
+ android:src="@mipmap/ic_launcher"
+ android:visibility="gone"
+ app:layout_constraintBottom_toTopOf="@id/progressBar"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent" />
+
<TextView
android:id="@+id/loadingText"
android:layout_width="0dp"
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────